/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <kernel/process.h>
#include <klib/lib.h>
#include <kernel/list.h>
#include <kernel/sched.h>
#include <kernel/errors.h>
#include <kernel/kmalloc.h>
#include <kernel/console.h>
#include <kernel/event.h>


struct process *current_process = null;



static struct process *processes[NR_PRIOS] = {0}, *processes_tail[NR_PRIOS] = {0};

static struct process *sleeps[NR_PRIOS] = {0}, *sleeps_tail[NR_PRIOS] = {0};

//The next process to be scheduled for each priority
static struct process *next_process[NR_PRIOS];


/*
 * This is called to figure out the next process after adding a new process
 * to the main list or moving from the sleep list
 */
static void update_next_process_add(struct process *added)
{
	//If this is the first process in this priority, we have to schedule it
	if(processes[added->prio] == null) {
		next_process[added->prio] = added;
	}
}

void sched_add_process(struct process *process, uint_t prio)
{
	process->state = PROCESS_STATE_SLEEP;
	process->prio = prio;
	list_attach(sleeps[prio], sleeps_tail[prio], process);
}

/*
 * This is called to figure out the next process after removing from the main
 * list or moving to the sleep list
 */
static void update_next_process_delete(struct process *deleted)
{
	if(deleted == next_process[deleted->prio]) {
		next_process[deleted->prio] = deleted->next;
		if(next_process[deleted->prio] == null) {
			//Reinitialize to head
			next_process[deleted->prio] = processes[deleted->prio];
		}
	}
}

/*
 * Change the priority of an already scheduled process, and hence change the
 * list it belongs to
 */
void sched_change_prio(struct process *process, uint_t new_prio)
{
	if(process->state == PROCESS_STATE_RUN) {
		update_next_process_delete(process);
		list_detach(processes[process->prio], processes_tail[process->prio], process);
		process->prio = new_prio;
		update_next_process_add(process);
		list_attach(processes[new_prio], processes_tail[new_prio], process);
	} else if(process->state == PROCESS_STATE_SLEEP) {
		list_detach(sleeps[process->prio], sleeps_tail[process->prio], process);
		process->prio = new_prio;
		list_attach(sleeps[new_prio], sleeps_tail[new_prio], process);
	}
}


void sched_remove_process(struct process *process)
{
	if(process->state == PROCESS_STATE_RUN) {
		update_next_process_delete(process);
		list_detach(processes[process->prio], processes_tail[process->prio], process);
		if(current_process == process)
			current_process = null;
	} else if(process->state == PROCESS_STATE_SLEEP) {
		list_detach(sleeps[process->prio], sleeps_tail[process->prio], process);
	}
}

void sched_sleep_process(struct process *process)
{
	if(process->state != PROCESS_STATE_RUN)
		assertv(false, "Process is already sleeping\n");
	update_next_process_delete(process);
	list_detach(processes[process->prio], processes_tail[process->prio], process);
	list_attach(sleeps[process->prio], sleeps_tail[process->prio], process);
	process->state = PROCESS_STATE_SLEEP;
	//Raise event
	event_raise(process->evt_sleep, null);
}

void sched_run_process(struct process *process)
{
	if(process->state != PROCESS_STATE_SLEEP)
		assertv(false, "Process is already running\n");
	update_next_process_add(process);
	list_detach(sleeps[process->prio], sleeps_tail[process->prio], process);
	list_attach(processes[process->prio], processes_tail[process->prio], process);
	process->state = PROCESS_STATE_RUN;
	event_raise(process->evt_run, null);
}

/*
 * This will modify the list passed. Hence we pass pointers
 */
void sched_wait_process(struct process_waitlist **waits,
		struct process_waitlist **waits_tail,
		struct process *process)
{
	sched_sleep_process(process);
	struct process_waitlist *wait = (struct process_waitlist *)
			kmalloc(sizeof(struct process_waitlist), "procwl");
	wait->next = wait->prev = null;
	wait->process = process;
	process->wait = wait;
	list_attach((*waits), (*waits_tail), wait);
}

void sched_wake_waitlist(struct process_waitlist **waits,
		struct process_waitlist **waits_tail)
{
	struct process_waitlist *wait;
	for(wait = (*waits); wait; ) {
		struct process_waitlist *next = wait->next;

		sched_run_process(wait->process);
		wait->process->wait = null;
		list_detach((*waits), (*waits_tail), wait);
		kfree(wait, sizeof(struct process_waitlist), "procwl");

		wait = next;
	}
}

static uint_t highest_prio()
{
	uint_t i;
	for(i = 0; i < NR_PRIOS; i++) {
		if(processes[i] != null)
			return i;
	}
	assertv(false, "sched_release called with no processes to run!\n");
	return 0;
}

void sched_release()
{
	struct process *next, *prev;
	uint_t prio = highest_prio();
	prev = current_process;
	next = next_process[prio];
	if(prev == next) {
		next = next->next;
		if(next == null) {
			next = processes[prio];
			//Really! No other process to run
			if(prev == next) {
				return;
			}
		}
	}

	process_switch(prev, next);
}
